#include "transcoder_core.h"

extern int OUTPUT_VIDEO_NUM;
int src_image_size;
extern EncodeStruct ess[6];

List *GOP_BUFFER = NULL;
sem_t GOP_BUFFER_MUTEX;

List **FULL_GOP_BUFFER = NULL;
sem_t *FULL_GOP_BUFFER_MUTEX;
sem_t *FULL_GOP_NUM;
sem_t COUNT_FULL_GOP_BUFFER_REF_GOP;

int gop_size;

bool is_empty_Gop(Gop *g)
{
	return (g->frame_cnt == 0);
}

void free_Gop(Gop *g)
{
	sem_wait(&g->gop_mutex);
	g->encoded_cnt++;
	if (g->encoded_cnt == OUTPUT_VIDEO_NUM) {
		free(g->buf);
		free(g);
		sem_post(&COUNT_FULL_GOP_BUFFER_REF_GOP);
	}
	sem_post(&g->gop_mutex);
	return;
}

Gop *new_Gop(int gop_id, int gop_size)
{
	Gop *g = xmalloc(sizeof(Gop));
	g->gop_id = gop_id;
	g->frame_cnt = 0;
	g->capacity = gop_size;
	g->buf = xmalloc(src_image_size * gop_size);
	g->valid = xcalloc(gop_size, sizeof(uint8_t));
	g->encoded_cnt = 0;
	sem_init(&(g->gop_mutex), 0, 1);
	return g;
}

Gop *find_gop_in_gop_buffer(int gop_id)
{
	if (is_empty_List(GOP_BUFFER)) {
		return NULL;
	} else {
		ListNode *curr = GOP_BUFFER->start;
		while(true) {
			if (((Gop *)curr->elem)->gop_id == gop_id) {
				return (Gop *)curr->elem;
			} else {
				if (curr == GOP_BUFFER->end) {
					return NULL;
				} else {
					curr = curr->next;
				}
			}
		}
	}
}

void add_gop_to_gop_buffer(Gop *g)
{
	ListNode * node = new_ListNode(g);
	insert_List(node, GOP_BUFFER);
	return;
}

void remove_gop_from_gop_buffer(Gop *g)
{
	ENSURES(g != NULL);
	ENSURES(!is_empty_List(GOP_BUFFER));
	remove_elem_List(GOP_BUFFER, g);
}

int count_gop_in_full_gop_buffer()
{
	int count = 0;
	int i;
	for (i = 0; i < OUTPUT_VIDEO_NUM; i++) {
		List * buffer = FULL_GOP_BUFFER[i];
		ListNode *node = buffer->start;
		while (node != buffer->end) {
			int is_contain = false;
			int j;
			for (j = i + 1; j < OUTPUT_VIDEO_NUM; j++) {
				if (is_contain_List(node->elem, FULL_GOP_BUFFER[j])) {
					is_contain = true;
					break;
				}
			}
			if (!is_contain) {
				count++;
			}
		}
	}
	return count;
}

void print_gop_id_of_full_gop_buffer()
{
	printf("Gop ids:");
	int i;
	for (i = 0; i < OUTPUT_VIDEO_NUM; i++) {
		List * buffer = FULL_GOP_BUFFER[i];
		ListNode *node = buffer->start;
		while (true) {
			if (node == NULL) {
				break;
			} else {
				printf(" %d", ((Gop *)node->elem)->gop_id);
				if (node == buffer->end) {
					break;
				} else {
					node = node->next;
				}
			}
		}
		printf("\t");
	}
	printf("\n");
}

void add_gop_to_full_gop_buffers(Gop *g)
{
	int i;
	for (i = 0; i < OUTPUT_VIDEO_NUM; i++) {
		add_gop_to_full_gop_buffer(g, ess[i].full_gop_buffer_id);
	}
#ifdef COUNT_GOP
	print_gop_id_of_full_gop_buffer();
#endif

	return;
}

Gop *remove_gop_from_full_gop_buffer(int full_gop_buffer_id)
{
	sem_wait(&FULL_GOP_NUM[full_gop_buffer_id]);
	sem_wait(&FULL_GOP_BUFFER_MUTEX[full_gop_buffer_id]);
	ListNode * node = remove_List(FULL_GOP_BUFFER[full_gop_buffer_id]);
	sem_post(&FULL_GOP_BUFFER_MUTEX[full_gop_buffer_id]);
	Gop *g = (Gop *)node->elem;
	free(node);
	return g;
}

void add_scaled_frame_to_gop_buffer(int pict_num, uint8_t **buf)
{
	int gop_id = pict_num / gop_size;
	int frame_id = pict_num % gop_size;

	sem_wait(&GOP_BUFFER_MUTEX);
	Gop *g = find_gop_in_gop_buffer(gop_id);
	if (g == NULL) {
		g = new_Gop(gop_id, gop_size);
		add_gop_to_gop_buffer(g);
	}
	sem_post(&GOP_BUFFER_MUTEX);

	sem_wait(&g->gop_mutex);
	memcpy(&g->buf[src_image_size * frame_id], buf[0], src_image_size);
	g->frame_cnt += 1;
	g->valid[frame_id] = 1;
	if (g->frame_cnt == gop_size) {
		remove_gop_from_gop_buffer(g);
		sem_wait(&COUNT_FULL_GOP_BUFFER_REF_GOP);
		add_gop_to_full_gop_buffers(g);
	}
	sem_post(&g->gop_mutex);

	return;
}

void add_gop_to_full_gop_buffer(Gop *g, int full_gop_buffer_id)
{
	ListNode * node = new_ListNode(g);
	sem_wait(&FULL_GOP_BUFFER_MUTEX[full_gop_buffer_id]);
	insert_List(node, FULL_GOP_BUFFER[full_gop_buffer_id]);
	sem_post(&FULL_GOP_BUFFER_MUTEX[full_gop_buffer_id]);
	sem_post(&FULL_GOP_NUM[full_gop_buffer_id]);
	return;
}

int add_remaining_frame_to_full_gop_buffer()
{
	while (length_List(GOP_BUFFER) > 0) {
		ListNode *node = remove_List(GOP_BUFFER);
		add_gop_to_full_gop_buffers((Gop *)node->elem);
		free(node);
	}
	return 0;
}
